package model.algorithms.splitting;

import ilog.concert.IloException;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloNumVarType;
import ilog.cplex.IloCplex;

import model.components.networks.network.VirtualNetwork;

public class Exact extends SplittingAlgorithm
{
	public Exact( String name )
	{
		super( name );
	}
	
	@Override
	protected int[] bestNodesMapping()
	{
		VirtualNetwork virtualNetwork = this.getVirtualNetwork();
		double linksMappingCost[][][][] = this.getLinksMappingCost();
		double nodesMappingCost[][] = this.getNodesMappingCost();
		int virtualNodesNumber = virtualNetwork.getNodes().size();
		int physicalNetworksNumber = this.getPhysicalNetworks().size();
		int bestNodesMapping[] = new int[ virtualNodesNumber ];
		
		try
		{
			IloCplex cplex = new IloCplex();
			cplex.setOut( null );
			
			// Branch and Bound
			cplex.setParam( IloCplex.DoubleParam.CutsFactor, 1.0 );
			cplex.setParam( IloCplex.IntParam.PreDual, 1 );
			cplex.setParam( IloCplex.IntParam.PPriInd, 4 );
			cplex.setParam( IloCplex.IntParam.Probe, -1 );
			cplex.setParam( IloCplex.IntParam.MCFCuts, -1 );
						
			IloNumVar x[][] = new IloNumVar[ physicalNetworksNumber ][];
			
			for( int i = 0; i < physicalNetworksNumber; i++ )
				x[ i ] = cplex.numVarArray( virtualNodesNumber, 0, 1, IloNumVarType.Int );
			
			IloNumVar y[][][][] = new IloNumVar[ physicalNetworksNumber ][][][];
			
			for( int i = 0; i < physicalNetworksNumber; i++ )
			{
				y[ i ] = new IloNumVar[ physicalNetworksNumber ][][];
				
				for( int j = 0; j < physicalNetworksNumber; j++ )
				{
					y[ i ][ j ] = new IloNumVar[ virtualNodesNumber ][];
					
					for( int k = 0; k < virtualNodesNumber; k++ )
						y[ i ][ j ][ k ] = cplex.numVarArray( virtualNodesNumber, 0, 1, IloNumVarType.Int );
				}
			}
			
			// Built the first summation
			IloLinearNumExpr nodes = cplex.linearNumExpr();
			
			for( int i = 0; i < physicalNetworksNumber; i++ )
				for( int j = 0; j < virtualNodesNumber; j++ )
					nodes.addTerm( nodesMappingCost[ i ][ j ], x[ i ][ j ] );
			
			// Build the second summation
			IloLinearNumExpr links = cplex.linearNumExpr();

			for( int i = 0; i < physicalNetworksNumber; i++ )
				for( int j = 0; j < physicalNetworksNumber; j++ )
					for( int k = 0; k < virtualNodesNumber; k++ )
						for( int l = k + 1; l < virtualNodesNumber; l++ )
							links.addTerm( linksMappingCost[ i ][ j ][ k ][ l ], y[ i ][ j ][ k ][ l ] );
			
			// The objective function
			IloNumExpr expr = cplex.sum( nodes, links );
			
			// Create objective minimization
			cplex.addMinimize( expr );

			// Constraint 1
			
			for( int i = 0; i < physicalNetworksNumber; i++ )
				for( int k = 0; k < virtualNodesNumber; k++ )
					for( int l = 0; l < virtualNodesNumber; l++ )
					{
						IloLinearNumExpr sum1 = cplex.linearNumExpr();
						
						for( int j = 0; j < physicalNetworksNumber; j++ )						
							sum1.addTerm( y[ i ][ j ][ k ][ l ], 1 );
						
						cplex.addEq( sum1, x[ i ][ k ] );
					}
			
			// Constraint 2
			
			for( int i = 0; i < physicalNetworksNumber; i++ )
				for( int j = 0; j < physicalNetworksNumber; j++ )
					for( int k = 0; k < virtualNodesNumber; k++ )
						for( int l = 0; l < virtualNodesNumber; l++ )
						{
							IloNumExpr sum2 = null;
							sum2 = cplex.sum( x[ i ][ k ], x[ j ][ l ] );
							sum2 = cplex.sum( sum2, cplex.prod( y[ i ][ j ][ k ][ l ], -1 ) );
							cplex.addLe( sum2, 1 );
						}
			
			// Constraint 3
			for( int j = 0; j < virtualNodesNumber; j++ )
			{
				IloLinearNumExpr sum3 = cplex.linearNumExpr();
				
				for( int i = 0; i < physicalNetworksNumber; i++ )
					sum3.addTerm( x[ i ][ j ], 1 );
				
				cplex.addEq( sum3, 1 );
			}
			
			// Constraint 4
			for( int i = 0; i < physicalNetworksNumber; i++ )
				for( int j = 0; j < physicalNetworksNumber; j++ )
					for( int k = 0; k < virtualNodesNumber; k++ )
						for( int l = 0; l < virtualNodesNumber; l++ )
							cplex.addEq( y[ i ][ j ][ k ][ l ], y[ j ][ i ][ l ][ k ] );
			
			if( cplex.solve() == false )
			{
				System.out.println( "denial" );
				cplex.end();
			}
			else
			{
				double xVar[][] = new double[ physicalNetworksNumber ][ virtualNodesNumber ];
				
				for( int i = 0; i < physicalNetworksNumber; i++ )
					xVar[ i ] = cplex.getValues( x[ i ] );	
				
				double yVar[][][][] = new double[ physicalNetworksNumber ][ physicalNetworksNumber ][ virtualNodesNumber ][ virtualNodesNumber ];
				
				for( int i = 0; i < physicalNetworksNumber; i++ )
					for( int j = 0; j < physicalNetworksNumber; j++ )
						for( int k = 0; k < virtualNodesNumber; k++ )
							for( int l = 0; l < virtualNodesNumber; l++ )
							{
								try
								{
									yVar[ i ][ j ][ k ][ l ] = cplex.getValue( y[ i ][ j ][ k ][ l ] );
								}
								catch( IloException e )
								{
									System.out.println( "error" );
								}
							}
				
				for( int i = 0; i < physicalNetworksNumber; i++ )
					for( int j = 0; j < virtualNodesNumber; j++ )
						if( xVar[ i ][ j ] != 0 )
							bestNodesMapping[ j ] = i;
			
				cplex.end();
			}
		}
		catch( IloException e )
		{
			System.err.println( "Concert exception caught: " + e );
		}

		return bestNodesMapping;
	}
	
}
